﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.IdentityModel.Claims;
using Sinacor.Infra.Common.Caching;
using PSE.Framework.ProviderLoader.Configuration;
using PSE.Framework.Configuration;
using PSE.Framework.ProviderLoader;

namespace Sinacor.Infra.Service.Security.Authorization
{
	/// <summary>
	/// Provê informações do contexto do usuário autenticado no serviço
	/// </summary>
	public abstract class BaseAuthorizationContext
	{
		private List<string> _moduleIds;
		private List<string> _installedModuleIds;

		protected abstract int GetUserId();
		protected abstract string GetLoginName();
		protected abstract string GetPassword();
		protected abstract int GetCompanyId();
		protected abstract string GetModuleId();

        private const string CONFIG_SECTION_NAME = "serviceProviders";

		/// <summary>
		/// Verifica se o usuário autenticado tem acesso a função especificada
		/// </summary>
		/// <param name="function">Função a ser validada</param>
		/// <returns>True se o usuário tiver acesso, caso contrário, false</returns>
		public bool CheckUserAccess(string function)
		{
			return this.CheckUserAccess(function, this.GetUserId(), this.GetCompanyId(), this.GetModuleId());
		}

        /// <summary>
        /// Verifica se o usuário autenticado tem acesso a função especificada
        /// </summary>
        /// <param name="function">Função a ser validada</param>
        /// <param name="moduleId">Modulo onde a função deve ser validada</param>
        /// <returns>True se o usuário tiver acesso, caso contrário, false</returns>
        public bool CheckUserAccess(string function, string moduleId)
        {
            return this.CheckUserAccess(function, this.GetUserId(), this.GetCompanyId(), moduleId);
        }

		/// <summary>
		/// Verifica se o usuário autenticado tem acesso a função especificada
		/// </summary>
		/// <param name="function">Função a ser validada</param>
		/// <returns>True se o usuário tiver acesso, caso contrário, false</returns>
		public bool CheckUserAccess(string function, int userId, int companyId, string moduleId)
		{
            IAuthorizationProvider authorizationProvider = AuthorizationProviderFactory.RetrieveAuthorizationProvider();
			string key = "checkUserAccess_" + userId + "_" + companyId;

			List<string> _functions = Cache.GetData(key) as List<string>;
			
            if (_functions == null)
			{
                _functions = authorizationProvider.RetrieveFunctions(userId, companyId, moduleId);
                Cache.Add(key, _functions, new TimeSpan(0, 30, 0));
			}

            foreach (string f in _functions)
            {
                if (f.Trim() == function.Trim())
                {
                    return true;
                }
            }

			//Se não encontrou nenhuma, retorna falso.
			return false;
		}

		/// <summary>
		/// Retornar as funcoes as quais o usuario tem acesso
		/// </summary>
		/// <returns></returns>
		public List<string> RetrieveUserRoles()
		{
			return this.RetrieveUserRoles(this.GetUserId(), this.GetCompanyId(), this.GetModuleId());
		}

		/// <summary>
		/// Retornar as funcoes as quais o usuario tem acesso, de acordo com o moduleId
		/// e userId e companyId do usuario autenticado
		/// </summary>
		/// <returns></returns>
		public List<string> RetrieveUserRoles(string moduleId)
		{
			return this.RetrieveUserRoles(this.GetUserId(), this.GetCompanyId(), moduleId);
		}

		/// <summary>
		/// Retornar as funcoes as quais o usuario tem acesso, de acordo com o companyId e moduleId informados
		/// e userId do usuario autenticado
		/// </summary>
		/// <returns></returns>
		public List<string> RetrieveUserRoles(int companyId, string moduleId)
		{
			return this.RetrieveUserRoles(this.GetUserId(), companyId, moduleId);
		}

		/// <summary>
		/// Retornar as funcoes as quais o usuario tem acesso, de acordo com o userId, companyId e moduleId
		/// </summary>
		/// <returns></returns>
		public List<string> RetrieveUserRoles(int userId, int companyId, string moduleId)
		{
			string key = "checkUserAccess_" + userId + "_" + companyId;

			if (!Cache.Contains(key))
			{
                ProviderConfigurationSection configSection = ApplicationConfiguration.GetCurrent().GetSection<ProviderConfigurationSection>(CONFIG_SECTION_NAME);

                IAuthorizationProvider authorizationProvider = ProviderLoader<IAuthorizationProvider>.LoadProvider(configSection.Providers, "AuthorizationProvider");

				Cache.Add(key, authorizationProvider.RetrieveFunctions(userId, companyId, moduleId));
			}

			return Cache.GetData(key) as List<string>;
		}

		/// <summary>
		/// Retorna os módulos que o usuário autenticado tem acesso.
		/// </summary>
		/// <returns>Lista com os id's dos módulos</returns>
		public List<string> RetrieveUserModules()
		{
			return this.RetrieveUserModules(this.GetUserId(), this.GetCompanyId());
		}

		/// <summary>
		/// Retorna os módulos que o usuário/empresa tem acesso.
		/// </summary>
		/// <param name="userId">Id do usuário</param>
		/// <param name="companyId">Id da empresa</param>
		/// <returns>Lista com os id's dos módulos</returns>
		public List<string> RetrieveUserModules(int userId, int companyId)
		{
			if (_moduleIds == null)
			{
                ProviderConfigurationSection configSection = ApplicationConfiguration.GetCurrent().GetSection<ProviderConfigurationSection>(CONFIG_SECTION_NAME);

                IAuthorizationProvider authorizationProvider = ProviderLoader<IAuthorizationProvider>.LoadProvider(configSection.Providers, "AuthorizationProvider");

				_moduleIds = authorizationProvider.RetrieveModuleIds(userId, companyId);
			}

			return _moduleIds;
		}

		/// <summary>
		/// Verifica se o moduleId está instalado
		/// </summary>
		/// <param name="moduleId">Módulo a ser validada</param>
		/// <returns>True se o módulo tiver instalado, caso contrário, false</returns>
		public bool VerifyInstalledModuleId(string moduleId)
		{
			return this.VerifyInstalledModuleId(moduleId, this.GetCompanyId());
		}

		/// <summary>
		/// Verifica se o moduleId está instalado
		/// </summary>
		/// <param name="moduleId">Módulo a ser validada</param>
		/// <returns>True se o módulo tiver instalado, caso contrário, false</returns>
		public bool VerifyInstalledModuleId(string moduleId, int companyId)
		{
			if (_installedModuleIds == null)
			{
				IAuthorizationProvider authorizationProvider = AuthorizationProviderFactory.RetrieveAuthorizationProvider();
				_installedModuleIds = authorizationProvider.RetrieveInstalledModuleIds(companyId);
			}

			return _installedModuleIds.Contains(moduleId);
		}
	}

	/// <summary>
	/// Provê informações do contexto do usuário autenticado no serviço
	/// </summary>
	public class AuthorizationContext : BaseAuthorizationContext
	{
		private int _userId;
		private string _loginName;
		private string _password;
		private int _companyId;
		private string _moduleId;

		private string _tokenId;
		private string _companyName;
		private string _companyCGC;
		private Int32? _sessionId;
		private bool? _sessionExpires;
		private ClientHost _host;

		/// <summary>
		/// Código do usuário
		/// </summary>
		public int UserId
		{
			get { return _userId; }
			set { _userId = value; }
		}
		/// <summary>
		/// Login do usuário
		/// </summary>
		public string LoginName
		{
			get { return _loginName; }
			set { _loginName = value; }
		}
		/// <summary>
		/// Password cifrado do usuário
		/// </summary>
		public string Password
		{
			get { return _password; }
			set { _password = value; }
		}
		/// <summary>
		/// Id da empresa
		/// </summary>
		public int CompanyId
		{
			get { return _companyId; }
			set { _companyId = value; }
		}
		/// <summary>
		/// Id do Módulo em execução
		/// </summary>
		public string ModuleId
		{
			get { return _moduleId; }
			set { _moduleId = value; }
		}


		/// <summary>
		/// Dados do client autenticado.
		/// </summary>
		public ClientHost ClientHost
		{
			get { return _host; }
			set { _host = value; }
		}

		/// <summary>
		/// Password cifrado do usuário
		/// </summary>
		public string TokenId
		{
			get { return _tokenId; }
			set { _tokenId = value; }
		}

		/// <summary>
		/// Nome da Empresa
		/// </summary>
		public string CompanyName
		{
			get { return _companyName; }
			set { _companyName = value; }
		}
		/// <summary>
		/// CGC da Empresa
		/// </summary>
		public string CompanyCGC
		{
			get { return _companyCGC; }
			set { _companyCGC = value; }
		}

		/// <summary>
		/// Id da sessão
		/// </summary>
		public Int32? SessionId
		{
			get { return _sessionId; }
			set { _sessionId = value; }
		}

		/// <summary>
		/// Indica se a sessão expira em um novo login ou não.
		/// </summary>
		public bool? SessionExpires
		{
			get { return _sessionExpires; }
			set { _sessionExpires = value; }
		}


		/// <summary>
		/// Retorna o contexto de autorização corrente.
		/// </summary>
		public static AuthorizationContext Current
		{
			get
			{
				return GetCurrent();
			}
			set
			{
				SetCurrent(value);
			}
		}

		private static Dictionary<int, AuthorizationContext> _authorizationList = new Dictionary<int, AuthorizationContext>();

		private static AuthorizationContext GetCurrent()
		{
			if (_authorizationList.ContainsKey(System.Threading.Thread.CurrentThread.ManagedThreadId))
				return _authorizationList[System.Threading.Thread.CurrentThread.ManagedThreadId];

			return null;
		}

		private static void SetCurrent(AuthorizationContext context)
		{
			if (!_authorizationList.ContainsKey(System.Threading.Thread.CurrentThread.ManagedThreadId))
				lock (_authorizationList)
				{
					_authorizationList.Add(System.Threading.Thread.CurrentThread.ManagedThreadId, context);
				}
			else
				_authorizationList[System.Threading.Thread.CurrentThread.ManagedThreadId] = context;

		}

		protected override int GetUserId()
		{
			return this.UserId;
		}

		protected override string GetLoginName()
		{
			return this.LoginName;
		}

		protected override string GetPassword()
		{
			return this.Password;
		}

		protected override int GetCompanyId()
		{
			return this.CompanyId;
		}

		protected override string GetModuleId()
		{
			return this.ModuleId;
		}
	}
}
